// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package com.feilong.lib.ognl;

import java.lang.reflect.Array;

import com.feilong.lib.ognl.enhance.ExpressionCompiler;
import com.feilong.lib.ognl.enhance.OrderedReturn;
import com.feilong.lib.ognl.enhance.UnsupportedCompilationException;

/**
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public class ASTChain extends SimpleNode implements NodeType,OrderedReturn{

    /**
     * 
     */
    private static final long serialVersionUID = 8632726828720933079L;

    private Class             _getterClass;

    private Class             _setterClass;

    private String            _lastExpression;

    private String            _coreExpression;

    public ASTChain(int id){
        super(id);
    }

    public ASTChain(OgnlParser p, int id){
        super(p, id);
    }

    @Override
    public String getLastExpression(){
        return _lastExpression;
    }

    @Override
    public String getCoreExpression(){
        return _coreExpression;
    }

    @Override
    public void jjtClose(){
        flattenTree();
    }

    @Override
    protected Object getValueBody(OgnlContext context,Object source) throws OgnlException{
        Object result = source;

        for (int i = 0, ilast = _children.length - 1; i <= ilast; ++i){
            boolean handled = false;

            if (i < ilast){
                if (_children[i] instanceof ASTProperty){
                    ASTProperty propertyNode = (ASTProperty) _children[i];
                    int indexType = propertyNode.getIndexedPropertyType(context, result);

                    if ((indexType != OgnlRuntime.INDEXED_PROPERTY_NONE) && (_children[i + 1] instanceof ASTProperty)){
                        ASTProperty indexNode = (ASTProperty) _children[i + 1];

                        if (indexNode.isIndexedAccess()){
                            Object index = indexNode.getProperty(context, result);

                            if (index instanceof DynamicSubscript){
                                if (indexType == OgnlRuntime.INDEXED_PROPERTY_INT){
                                    Object array = propertyNode.getValue(context, result);
                                    int len = Array.getLength(array);

                                    switch (((DynamicSubscript) index).getFlag()) {
                                        case DynamicSubscript.ALL:
                                            result = Array.newInstance(array.getClass().getComponentType(), len);
                                            System.arraycopy(array, 0, result, 0, len);
                                            handled = true;
                                            i++;
                                            break;
                                        case DynamicSubscript.FIRST:
                                            index = new Integer((len > 0) ? 0 : -1);
                                            break;
                                        case DynamicSubscript.MID:
                                            index = new Integer((len > 0) ? (len / 2) : -1);
                                            break;
                                        case DynamicSubscript.LAST:
                                            index = new Integer((len > 0) ? (len - 1) : -1);
                                            break;
                                    }
                                }else{
                                    if (indexType == OgnlRuntime.INDEXED_PROPERTY_OBJECT){
                                        throw new OgnlException(
                                                        "DynamicSubscript '" + indexNode + "' not allowed for object indexed property '"
                                                                        + propertyNode + "'");
                                    }
                                }
                            }
                            if (!handled){
                                result = OgnlRuntime.getIndexedProperty(
                                                context,
                                                result,
                                                propertyNode.getProperty(context, result).toString(),
                                                index);
                                handled = true;
                                i++;
                            }
                        }
                    }
                }
            }
            if (!handled){
                result = _children[i].getValue(context, result);
            }
        }
        return result;
    }

    @Override
    protected void setValueBody(OgnlContext context,Object target,Object value) throws OgnlException{
        boolean handled = false;

        for (int i = 0, ilast = _children.length - 2; i <= ilast; ++i){
            if (i <= ilast){
                if (_children[i] instanceof ASTProperty){
                    ASTProperty propertyNode = (ASTProperty) _children[i];
                    int indexType = propertyNode.getIndexedPropertyType(context, target);

                    if ((indexType != OgnlRuntime.INDEXED_PROPERTY_NONE) && (_children[i + 1] instanceof ASTProperty)){
                        ASTProperty indexNode = (ASTProperty) _children[i + 1];

                        if (indexNode.isIndexedAccess()){
                            Object index = indexNode.getProperty(context, target);

                            if (index instanceof DynamicSubscript){
                                if (indexType == OgnlRuntime.INDEXED_PROPERTY_INT){
                                    Object array = propertyNode.getValue(context, target);
                                    int len = Array.getLength(array);

                                    switch (((DynamicSubscript) index).getFlag()) {
                                        case DynamicSubscript.ALL:
                                            System.arraycopy(target, 0, value, 0, len);
                                            handled = true;
                                            i++;
                                            break;
                                        case DynamicSubscript.FIRST:
                                            index = new Integer((len > 0) ? 0 : -1);
                                            break;
                                        case DynamicSubscript.MID:
                                            index = new Integer((len > 0) ? (len / 2) : -1);
                                            break;
                                        case DynamicSubscript.LAST:
                                            index = new Integer((len > 0) ? (len - 1) : -1);
                                            break;
                                    }
                                }else{
                                    if (indexType == OgnlRuntime.INDEXED_PROPERTY_OBJECT){
                                        throw new OgnlException(
                                                        "DynamicSubscript '" + indexNode + "' not allowed for object indexed property '"
                                                                        + propertyNode + "'");
                                    }
                                }
                            }
                            if (!handled && i == ilast){
                                OgnlRuntime.setIndexedProperty(
                                                context,
                                                target,
                                                propertyNode.getProperty(context, target).toString(),
                                                index,
                                                value);
                                handled = true;
                                i++;
                            }else if (!handled){
                                target = OgnlRuntime.getIndexedProperty(
                                                context,
                                                target,
                                                propertyNode.getProperty(context, target).toString(),
                                                index);
                                i++;
                                continue;
                            }
                        }
                    }
                }
            }
            if (!handled){
                target = _children[i].getValue(context, target);
            }
        }
        if (!handled){
            _children[_children.length - 1].setValue(context, target, value);
        }
    }

    @Override
    public boolean isSimpleNavigationChain(OgnlContext context) throws OgnlException{
        boolean result = false;

        if ((_children != null) && (_children.length > 0)){
            result = true;
            for (int i = 0; result && (i < _children.length); i++){
                if (_children[i] instanceof SimpleNode){
                    result = ((SimpleNode) _children[i]).isSimpleProperty(context);
                }else{
                    result = false;
                }
            }
        }
        return result;
    }

    @Override
    public Class getGetterClass(){
        return _getterClass;
    }

    @Override
    public Class getSetterClass(){
        return _setterClass;
    }

    @Override
    public String toString(){
        String result = "";

        if ((_children != null) && (_children.length > 0)){
            for (int i = 0; i < _children.length; i++){
                if (i > 0){
                    if (!(_children[i] instanceof ASTProperty) || !((ASTProperty) _children[i]).isIndexedAccess()){
                        result = result + ".";
                    }
                }
                result += _children[i].toString();
            }
        }
        return result;
    }

    @Override
    public String toGetSourceString(OgnlContext context,Object target){
        String prevChain = (String) context.get("_currentChain");

        if (target != null){
            context.setCurrentObject(target);
            context.setCurrentType(target.getClass());
        }

        String result = "";
        NodeType _lastType = null;
        boolean ordered = false;
        boolean constructor = false;
        try{
            if ((_children != null) && (_children.length > 0)){
                for (int i = 0; i < _children.length; i++){
                    /*
                     * System.out.println("astchain child: " + _children[i].getClass().getName()
                     * + " with current object target " + context.getCurrentObject()
                     * + " current type: " + context.getCurrentType());
                     */

                    String value = _children[i].toGetSourceString(context, context.getCurrentObject());

                    //                    System.out.println("astchain child returned >>  " + value + "  <<");

                    if (ASTCtor.class.isInstance(_children[i])){
                        constructor = true;
                    }

                    if (NodeType.class.isInstance(_children[i]) && ((NodeType) _children[i]).getGetterClass() != null){
                        _lastType = (NodeType) _children[i];
                    }

                    //                    System.out.println("Astchain i: " + i + " currentobj : " + context.getCurrentObject() + " and root: " + context.getRoot());
                    if (!ASTVarRef.class.isInstance(_children[i]) && !constructor
                                    && !(OrderedReturn.class.isInstance(_children[i])
                                                    && ((OrderedReturn) _children[i]).getLastExpression() != null)
                                    && (_parent == null || !ASTSequence.class.isInstance(_parent))){
                        value = OgnlRuntime.getCompiler().castExpression(context, _children[i], value);
                    }

                    /*
                     * System.out.println("astchain value now : " + value + " with index " + i
                     * + " current type " + context.getCurrentType() + " current accessor " + context.getCurrentAccessor()
                     * + " prev type " + context.getPreviousType() + " prev accessor " + context.getPreviousAccessor());
                     */

                    if (OrderedReturn.class.isInstance(_children[i]) && ((OrderedReturn) _children[i]).getLastExpression() != null){
                        ordered = true;
                        OrderedReturn or = (OrderedReturn) _children[i];

                        if (or.getCoreExpression() == null || or.getCoreExpression().trim().length() <= 0){
                            result = "";
                        }else{
                            result += or.getCoreExpression();
                        }

                        _lastExpression = or.getLastExpression();

                        if (context.get(ExpressionCompiler.PRE_CAST) != null){
                            _lastExpression = context.remove(ExpressionCompiler.PRE_CAST) + _lastExpression;
                        }
                    }else if (ASTOr.class.isInstance(_children[i]) || ASTAnd.class.isInstance(_children[i])
                                    || ASTCtor.class.isInstance(_children[i])
                                    || (ASTStaticField.class.isInstance(_children[i]) && _parent == null)){
                        context.put("_noRoot", "true");
                        result = value;
                    }else{
                        result += value;
                    }

                    context.put("_currentChain", result);
                }
            }
        }catch (Throwable t){
            throw OgnlOps.castToRuntime(t);
        }

        if (_lastType != null){
            _getterClass = _lastType.getGetterClass();
            _setterClass = _lastType.getSetterClass();
        }

        if (ordered){
            _coreExpression = result;
        }

        context.put("_currentChain", prevChain);

        return result;
    }

    @Override
    public String toSetSourceString(OgnlContext context,Object target){
        String prevChain = (String) context.get("_currentChain");
        String prevChild = (String) context.get("_lastChild");

        if (prevChain != null){
            throw new UnsupportedCompilationException("Can't compile nested chain expressions.");
        }

        if (target != null){
            context.setCurrentObject(target);
            context.setCurrentType(target.getClass());
        }

        String result = "";
        NodeType _lastType = null;
        boolean constructor = false;
        try{
            if ((_children != null) && (_children.length > 0)){
                if (ASTConst.class.isInstance(_children[0])){
                    throw new UnsupportedCompilationException("Can't modify constant values.");
                }

                for (int i = 0; i < _children.length; i++){
                    //                    System.out.println("astchain setsource child[" + i + "] : " + _children[i].getClass().getName());

                    if (i == (_children.length - 1)){
                        context.put("_lastChild", "true");
                    }

                    String value = _children[i].toSetSourceString(context, context.getCurrentObject());
                    //if (value == null || value.trim().length() <= 0)
                    //  return "";

                    //                    System.out.println("astchain setter child returned >>  " + value + "  <<");

                    if (ASTCtor.class.isInstance(_children[i])){
                        constructor = true;
                    }

                    if (NodeType.class.isInstance(_children[i]) && ((NodeType) _children[i]).getGetterClass() != null){
                        _lastType = (NodeType) _children[i];
                    }

                    if (!ASTVarRef.class.isInstance(_children[i]) && !constructor
                                    && !(OrderedReturn.class.isInstance(_children[i])
                                                    && ((OrderedReturn) _children[i]).getLastExpression() != null)
                                    && (_parent == null || !ASTSequence.class.isInstance(_parent))){
                        value = OgnlRuntime.getCompiler().castExpression(context, _children[i], value);
                    }

                    //                    System.out.println("astchain setter after cast value is: " + value);

                    /*
                     * if (!constructor && !OrderedReturn.class.isInstance(_children[i])
                     * && (_parent == null || !ASTSequence.class.isInstance(_parent)))
                     * {
                     * value = OgnlRuntime.getCompiler().castExpression(context, _children[i], value);
                     * }
                     */

                    if (ASTOr.class.isInstance(_children[i]) || ASTAnd.class.isInstance(_children[i])
                                    || ASTCtor.class.isInstance(_children[i]) || ASTStaticField.class.isInstance(_children[i])){
                        context.put("_noRoot", "true");
                        result = value;
                    }else{
                        result += value;
                    }

                    context.put("_currentChain", result);
                }
            }
        }catch (Throwable t){
            throw OgnlOps.castToRuntime(t);
        }

        context.put("_lastChild", prevChild);
        context.put("_currentChain", prevChain);

        if (_lastType != null){
            _setterClass = _lastType.getSetterClass();
        }

        return result;
    }

    @Override
    public boolean isChain(OgnlContext context){
        return true;
    }
}
